﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using FluentValidation;
using FluentValidation.Results;

namespace WSManager.Wpf
{
    public class ViewModelBase<T> : INotifyPropertyChanged, INotifyDataErrorInfo
    {
        protected T Item;
        protected ValidationResult ValidationResult { get; private set; }
        protected AbstractValidator<T> Validator { get; set; }
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            // take a copy to prevent thread issues
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
            if (propertyName != "HasErrors") { Validate(); }
        }

        protected void RaisePropertyChanged(params string[] propertyNames)
        {
            if (PropertyChanged != null)
            {
                foreach (var propName in propertyNames)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propName));
                }
            }
            if (propertyNames.First(w => w != "HasErrors") != null) { Validate(); }
        }

        private void Validate()
        {
            if (Validator == null) return;
            var result = Validator.Validate(Item);
            if (ValidationResult == null)
            {
                if (ErrorsChanged != null)
                    foreach (var error in result.Errors)
                    {
                        ErrorsChanged(this, new DataErrorsChangedEventArgs(error.PropertyName));
                    }
            }
            else if (ErrorsChanged != null)
                foreach (
                    var error in
                        this.ValidationResult.Errors.Join(result.Errors, t => t.PropertyName, t => t.PropertyName,
                            (o, n) => new Tuple<ValidationFailure, ValidationFailure>(o, n))
                            .Where(t => t.Item1.ErrorMessage != t.Item2.ErrorMessage).Select(t => t.Item2)
                            .Concat(this.ValidationResult.Errors.Where(x => result.Errors.FirstOrDefault(t => t.PropertyName == x.PropertyName) == null))
                            .Concat(result.Errors.Where(x => ValidationResult.Errors.FirstOrDefault(t => t.PropertyName == x.PropertyName) == null)))
                {
                    ErrorsChanged(this, new DataErrorsChangedEventArgs(error.PropertyName));
                }
            ValidationResult = result;
            ThreadPool.QueueUserWorkItem(s =>
                RaisePropertyChanged("HasErrors"));
        }


        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            return ValidationResult.Errors.Where(t => t.PropertyName == propertyName).Select(t => t.ErrorMessage);
        }

        public bool HasErrors
        {
            get
            {
                if (Validator == null) return false;
                if (ValidationResult == null) Validate();
                return !ValidationResult.IsValid;
            }
        }
    }
}
